package org.libarae.server;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.libarae.communication.ServerInterface;
import org.libarae.server.exceptions.ConfigParseException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * @author Tyler Holzer
 *
 * Server.java -- A part of Libarae
 * initializes the server and Waits for incoming socket requests
 */
public class Server {

	/**
	 * The server socket to listen for requests on
	 */
	private ServerSocket master;
	/**
	 * The configuration file for the server
	 */
	public static ServerConfig config;
	/**
	 * The DataManager for the server, limits access to the active user hash table
	 * as well as the database and external database
	 */
	public static DataManager manager=null;
	
	/**
	 * Constructor
	 */
	public Server() {
		config=new ServerConfig();
		parseConfig(); // get the server configuration details
		if(!config.imageFolderCreated()) { // create image directory if it doesn't exist
		    File f = new File("images"); 
            f.mkdir();
            config.setImagesFolderCreated(1);
		}
		manager=new DataManager(config.getDBExists(),config.getDBType()); // create databases
		manager.start(); // start the timeout function
		config.setDBExists(1); //After database is created database mark that it now exists
		buildXML();  // save config details to file
	}
	
	/**
	 * Main method, creates and starts the server as well as adding a shutdown hook to close the database
	 * 
	 * @param args -- the arguments from the command line
	 */
	public static void main(String[] args) {
		Server s=new Server();
		Runtime.getRuntime().addShutdownHook(new Thread(){
			public void run() {
				Server.manager.closeDB();
			}
		});
		s.run();
	}
	
	/**
	 * waits for incoming requests on the master socket and spawns a new thread to handle it
	 */
	public void run() {
		//Creating master socket on port
		try {
			master=new ServerSocket(config.getPort());
		}
		catch(IOException e) {
			if(Server.config.inDebugMode())
				e.printStackTrace();
			System.out.println("Error obtaining port: "+config.getPort()+"\nExiting...");
			System.exit(-1);
		}
		//wait and accept sockets
		while(true) {
			Socket slave=null;
			try {
				slave=master.accept();
			}
			catch(IOException e) {
				if(Server.config.inDebugMode())
					e.printStackTrace();
				System.out.println("Error accepting on port: "+config.getPort()+"\nExiting...");
				System.exit(-1);
			}
			new OperationThread(new ServerInterface(slave)).start();
		}
	}
	
	/**
	 * attempts to read in the config file. If there isn't one obtain the data required from the user
	 */
	public void parseConfig()  {
		File f=new File("config.xml");
		if(f.exists()) { //check that the file exists before trying to parse it
			try {
				 SAXParserFactory factory = SAXParserFactory.newInstance();
			     SAXParser saxParser = factory.newSAXParser();
			     
			     ConfigHandler handler =new ConfigHandler(config);
			     
			     saxParser.parse("config.xml", handler);
			}
			catch (Exception e) {
				if(Server.config.inDebugMode())//DEBUG MESSAGE
					e.printStackTrace();
				System.out.println("SERVER ERROR:\nAn error has occured while reading config.xml\nPlease verify that no invalid changes have been made to the file and restart the server");
				System.out.println("If this problem persists, please delete(or rename) the config.xml and reconfigure the server by starting the server again");
				System.exit(-1);
			}
		}
		else {
			initialConfig(); // file was not found, get the information from the user
		}
		try {
			config.configError();
		}
		catch(ConfigParseException e) {
			if(Server.config.inDebugMode())//DEBUG MESSAGE
				e.printStackTrace();
			//TODO add code to get the right data from the user rather than simply exiting the program
			System.out.println("An error occured when obtaining data from the XML,Please delete the config.xml, restart the server, and answer the questions presented to you.");
			System.exit(-1);
		}
	}

	/**
	 * Asks the user questions in order to obtain data necessary to run the server 
	 */
	public void initialConfig() {
		Scanner s = new Scanner(System.in);
		boolean cont=false;
		String tmp="";
		//get Database type
		while(!cont) {
			System.out.println("Please specify the type of database that you will be using (currently supports mySQL and sqlite):");
			tmp=s.nextLine();
			if(tmp.equalsIgnoreCase("sqlite")) {
				config.setDBType(0);
				cont=true;
			}
			else if(tmp.equalsIgnoreCase("mysql")) {
				config.setDBType(1);
				cont=true;
			}
			else
				System.out.println("Invalid Database type");
		}
		//get whether a database exists
		cont=false;
		while(!cont) {
			System.out.println("Has a database been previously been initialized by Libarae? Please answer yes or no.\nWarning: If a database has been previously initialized and filled, all data will be deleted if you answer \"no.\"");
			tmp=s.nextLine();
			if(tmp.equalsIgnoreCase("yes")) {
				config.setDBExists(1);
				cont=true;
			}
			else if(tmp.equalsIgnoreCase("no")) {
				config.setDBExists(0);
				cont=true;
			}
			else
				System.out.println("Your answer is not an acceptable answer");
		}
		//check if an image folder has been created
		cont=false;
		while(!cont) {
			System.out.println("Has a image folder been previously created? Please answer yes or no.\nNote:If you have successfully run the server in it's current location, the answer is yes.");
			tmp=s.nextLine();
			if(tmp.equalsIgnoreCase("yes")) {
				config.setImagesFolderCreated(1);
				cont=true;
			}
			else if(tmp.equalsIgnoreCase("no")) {
				config.setImagesFolderCreated(0);
				cont=true;
			}
			else
				System.out.println("Your answer is not an acceptable answer");
		}
		//get and ISBNdb access key from the user
		cont=false;
		while(!cont) {
			System.out.println("Please enter a ISBNdb Access key.\nIf you do not have one, please set up an account and obtain a key from https://isbndb.com/account/create.html");
			tmp=s.nextLine();
			config.setAccessKey(tmp); //TODO figure out if this can be verified
			cont=true;
		}
	}
	
	/**
	 * creates and writes a new XML file with the data in config
	 */
	public static void buildXML() {
		try{
			
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			 
			//root element
			Document doc = docBuilder.newDocument();
			Element rootElement = doc.createElement("libarae");
			doc.appendChild(rootElement);
			 
			//database element
			Element database = doc.createElement("database");
			rootElement.appendChild(database);
			
			//database type element of database  
			Element dbType = doc.createElement("database_type");
			if(config.getDBType()==0)
				dbType.appendChild(doc.createTextNode("sqlite"));
			else if(config.getDBType()==1)
				dbType.appendChild(doc.createTextNode("mysql"));
			database.appendChild(dbType);
			
			//database exists element of database
			Element dbExists = doc.createElement("database_exists");
			if(config.getDBExists())
				dbExists.appendChild(doc.createTextNode("true"));
			else if(!config.getDBExists())
				dbExists.appendChild(doc.createTextNode("false"));
			database.appendChild(dbExists);
			
			//image folder exists element of the database
			Element imgFolder = doc.createElement("image_folder_created");
			if(config.imageFolderCreated())
				imgFolder.appendChild(doc.createTextNode("true"));
			else if(!config.imageFolderCreated())
				imgFolder.appendChild(doc.createTextNode("false"));
			database.appendChild(imgFolder);
			
			//ISBNdb access key element of the database
			Element accessKey = doc.createElement("ISBNdb_Access_Key");
			accessKey.appendChild(doc.createTextNode(config.getAccessKey()));
			database.appendChild(accessKey);
			 
			//Server element
			Element server = doc.createElement("server");
			rootElement.appendChild(server);
			
			//port element of the server
			Element	port = doc.createElement("port");
			port.appendChild(doc.createTextNode(config.getPort()+""));
			server.appendChild(port);
			
			//pwdRequired element of the server
			Element pwdRequired = doc.createElement("REQUIRES_PASSWORD");
			if(config.passwordRequired())
				pwdRequired.appendChild(doc.createTextNode("true"));
			else if(!config.passwordRequired())
				pwdRequired.appendChild(doc.createTextNode("false"));
			server.appendChild(pwdRequired);
			
			//debugMode element of the server
			Element debugMode = doc.createElement("DEBUG_MODE");
			if(config.inDebugMode())
				debugMode.appendChild(doc.createTextNode("true"));
			else if(!config.inDebugMode())
				debugMode.appendChild(doc.createTextNode("false"));
			server.appendChild(debugMode);
			 
			//write the content into xml file
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			DOMSource source = new DOMSource(doc);
			StreamResult result =  new StreamResult(new File("config.xml"));
			transformer.transform(source, result);
		}
		catch(Exception e){
			if(Server.config.inDebugMode())//DEBUG MESSAGE
				e.printStackTrace();
			System.out.println("An error has occured while writing the config file to harddrive, please check your permissions and restart the serve\nexiting...");
			System.exit(-1);
		}
	}
}
